﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Magic.Framework.OQL;
using Magic.Framework.OQL.Expressions;
using Ddbs.QE.Parser.Front.Serialization;

namespace Ddbs.QE.Parser.Front.Factory
{
    public class SQLSemanticer
    {
        private static Dictionary<string, HashSet<string>> col2tbls;
        private static Dictionary<string, HashSet<string>> tbl2cols;
        private static Dictionary<string, Ddbs.QE.Parser.Front.Serialization.Table> name2table;
        private static Dictionary<string, Ddbs.QE.Parser.Front.Serialization.Column> name2column;

        private static void init(Conf conf)
        {
            col2tbls = new Dictionary<string, HashSet<string>>();
            tbl2cols = new Dictionary<string, HashSet<string>>();
            name2table = new Dictionary<string, Serialization.Table>();
            name2column = new Dictionary<string, Serialization.Column>();

            List<Ddbs.QE.Parser.Front.Serialization.Table> tables = conf.Database.Tables;
            foreach (Ddbs.QE.Parser.Front.Serialization.Table t in tables)
            {
                tbl2cols[t.Name] = new HashSet<string>();
                name2table[t.Name] = t;
                foreach (Ddbs.QE.Parser.Front.Serialization.Column c in t.Columns)
                {
                    if (!col2tbls.ContainsKey(c.ColumnName))
                    {
                        col2tbls[c.ColumnName] = new HashSet<string>();
                    }
                    name2column[t.Name + "." + c.ColumnName] = c;
                    col2tbls[c.ColumnName].Add(t.Name);
                    tbl2cols[t.Name].Add(c.ColumnName);
                }
            }
        }
        private static bool checkAndFillColumn(ref Ddbs.QE.Parser.Front.Serialization.Column c, HashSet<string> localTbls)
        {
            if (c.HasTableName)
            {
                if (!localTbls.Contains(c.TableName))
                {
                    return false;
                }
                if (!c.IsAllColumn)
                {
                    if (!tbl2cols[c.TableName].Contains(c.ColumnName))
                    {
                        return false;
                    }
                    c = name2column[c.TableName + "." + c.ColumnName];
                }
                
            }
            else
            {
                if (!c.IsAllColumn)
                {
                    if (!col2tbls.ContainsKey(c.ColumnName) )
                    {
                        return false;
                    }
                    HashSet<string> tmpTbls = col2tbls[c.ColumnName];
                    int hit = 0;
                    string hitTable="";
                    foreach (string s in localTbls)
                    {
                        if (tmpTbls.Contains(s))
                        {
                            hitTable = s;
                            ++hit;
                            if (hit == 2)
                            {
                                return false;
                            }
                        }
                    }
                    if (hit != 1)
                    {
                        return false;
                    }
                    c = name2column[hitTable + "." + c.ColumnName];
                }
            }
            return true;
        }
        private static bool checkAndFillColumns(SQLContext context,  HashSet<string> localTbls)
        {
            
            //check that all tables in where statement are in schema
            //replace shadow table with real ones
            for (int i = 0; i < context.Tables.Count;++i )
            {
                if (!(tbl2cols.ContainsKey(context.Tables.ElementAt(i).Name)))
                {
                    return false;
                }
                localTbls.Add(context.Tables.ElementAt(i).Name);
                string tmp = context.Tables.ElementAt(i).Name;
                context.Tables.RemoveAt(i);
                context.Tables.Insert(i, name2table[tmp]);
            }

            for (int i=0;i < context.Columns.Count;++i )
            {
                Ddbs.QE.Parser.Front.Serialization.Column tmp = context.Columns.ElementAt(i);
                if (!checkAndFillColumn(ref tmp, localTbls))
                {
                    return false;
                }
                context.Columns.RemoveAt(i);
                context.Columns.Insert(i, tmp);
            }
            return true;
        }

        private static bool checkCondition(Ddbs.QE.Parser.Front.Serialization.Condition condition, HashSet<string> localTbls)
        {
            if (condition == null)
                return true;
            if (condition.Type < ConditionType.LT)
            {
                return checkCondition(condition.Children.First(), localTbls) && checkCondition(condition.Children.ElementAt(1), localTbls);
            }
            if (condition.Type < ConditionType.NOT)
            {
                if (!(checkCondition(condition.Children.First(), localTbls) && checkCondition(condition.Children.ElementAt(1), localTbls)))
                {
                    return false;
                }
                bool[] type={false,false};
                for (int i=0;i<2;++i)
                {
                    if (condition.Children.ElementAt(i).Type == ConditionType.COLUMN)
                    {
                        Ddbs.QE.Parser.Front.Serialization.ColumnVal val = condition.Children.First() as ColumnVal;
                        type[i] = (val.ColumnRef.DataType == DataType.VCAHR);
                    }
                    else { 
                        type[i] = (condition.Children.ElementAt(i).Type == ConditionType.STRVAL);
                    }
                }
                if (type[0] != type[1])
                {
                    return false;
                }
                if (type[0])
                {
                    if (condition.Type != ConditionType.EQ)
                    {
                        return false;
                    }
                }
                return true;
            }
            if (condition.Type == ConditionType.NOT)
            {
                return checkCondition(condition.Children.First(), localTbls);
            }
            if (condition.Type == ConditionType.COLUMN)
            {
                ColumnVal c = condition as ColumnVal;
                Ddbs.QE.Parser.Front.Serialization.Column tmp = c.ColumnRef;
                if (!checkAndFillColumn(ref tmp, localTbls))
                {
                    return false;
                }
                c.ColumnRef = tmp;
            }
            return true;
        }

        private static bool matchTable(Serialization.Column c, string tablename)
        {
            return false;
        }
        

        private static void handleAllColumns(SQLContext context)
        {

            List<Serialization.Column> columns = new List<Serialization.Column>();

            foreach(Serialization.Column column in context.Columns)
            {
                if (column.IsAllColumn)
                {
                    if (column.HasTableName)
                    {
                        foreach(Serialization.Column c in (Serialization.ConfHelper.name2table)[column.TableName].Columns)
                        {
                            columns.Add(c);
                        }
                    }
                    else
                    {
                        columns.Clear();
                        foreach(Serialization.Table table in context.Tables)
                        {
                            foreach(Serialization.Column c in (Serialization.ConfHelper.name2table)[table.Name].Columns)
                        {
                            columns.Add(c);
                        }


                        }

                    }
                }
                else {
                    columns.Add(column);
                }
            }

            context.Columns = columns;

        }

        public static bool check(SQLContext context, Conf conf)
        {
            if (context == null 
                || context.Columns ==null 
                || context.Columns.Count == 0 
                || context.Tables == null
                || context.Tables.Count == 0)
                return false;

            if (tbl2cols == null || col2tbls == null)
            {
                init(conf);
            }
            HashSet<string> localTbls = new HashSet<string>();
            if (!checkAndFillColumns(context, localTbls))
            {
                return false;
            }
            if (!checkCondition(context.Cond, localTbls))
            {
                return false;
            }
            handleAllColumns(context);


            return true;
        }
    }
}
